package tsdb

import "time"

//"log"

// RollFunc is the type of a function that can compute rolling aggregations
// of a timeseries.
type RollFunc func(rd *RollData)

// IndexedRullFunc is the type of a function that can compute a rolling
// aggregation for specific vars in a timeseries.
type IndexedRollFunc func(*RollData, []int)

// RollAvg implements an IndexedRollFunc for a rolling average.
func RollAvg(rd *RollData, idxs []int) {
	for _, i := range idxs {
		fv := rd.AccV[i]
		//log.Printf("Setting AccV to %.2f / %.2f at index %d", fv, float64(rd.Cnt), i)
		rd.AccV[i] = fv / float64(rd.Cnt)
		//log.Printf("AccV %.2f", rd.AccV[i])
	}
}

// RollAvg implements an IndexedRollFunc for a rolling rate.
func RollRate(rd *RollData, idxs []int) {
	for _, i := range idxs {
		fv := rd.SV[i]
		//log.Printf("AccV was %.2f at index %d", rd.AccV[i], i)
		//log.Printf("Setting AccV to %.2f - %.2f at index %d", rd.LV[i], fv, i)
		rd.AccV[i] = rd.LV[i] - fv
		//log.Printf("AccV set to %v at index %d", rd.AccV[i], i)
	}
}

// RollingAggFunc turns a RollFunc into an AggFunc
func RollingAggFunc(f RollFunc, d time.Duration) AggFunc {
	return func(ch []SeriesResponse) []SeriesResponse {
		//fmt.Printf("In RolingAggFunc func")
		return Rolling(ch, f, d)
	}
}

// IdentityRollFunc does no rolling aggregation. It leaves the timeseries
// unchanged.
func IdentityRollFunc(rd *RollData, idxs []int) {
	for _, i := range idxs {
		//log.Printf("Setting AccV to %.2f at index %d", rd.SV[i], i)
		rd.AccV[i] = rd.SV[i]
	}
	//log.Printf("XXX: using identity roll func %v at ts %q", rd.AccV, rd.S)
}

// IndexedRollFuncMuxer composes a set of IndexdRollFuncs into a single
// AggFunc
func IndexedRollFuncMuxer(
	lookup map[string]IndexedRollFunc, fs map[string][]int, d time.Duration) AggFunc {
	rf := func(rd *RollData) {
		//log.Printf("XXX: In IndexedRollFuncMuxer %#v", rd)
		for fname, is := range fs {
			if fn, ok := lookup[fname]; ok {
				fn(rd, is)
			} else {
				panic("Unknown aggregation function " + fname)
			}
		}
	}
	return RollingAggFunc(rf, d)
}

func makeRollData(r *SeriesResponse) *RollData {
	sz := len(r.Val.Value)
	vs := make([]float64, sz)
	min := make([]float64, sz)
	for i, fv := range r.Val.Value {
		if fv != nil {
			vs[i] = *fv
			min[i] = *fv
		}
	}
	return &RollData{
		S: r.Val.Ts, L: r.Val.Ts, LV: vs, SV: vs,
		AccV: make([]float64, sz),
		Max:  make([]float64, sz),
		Min:  min,
		Cnt:  0,
	}
}

func updateRollData(r *SeriesResponse, rd *RollData) {
	vs := make([]float64, len(r.Val.Value))
	for i, fv := range r.Val.Value {
		if fv != nil {
			vs[i] = *fv
		}
	}
	rd.L = r.Val.Ts
	rd.LV = vs
	for i, fv := range r.Val.Value {
		if fv != nil {
			//log.Printf("Accumulating %2.f into %.2f at index %d\n", *fv, rd.AccV[i], i)
			rd.AccV[i] += *fv
			//log.Printf("Accumulation %2.f at index %d\n", rd.AccV[i], i)
			if *fv > rd.Max[i] {
				//log.Printf("Updating max at index %d to %.2f\n", i, *fv)
				rd.Max[i] = *fv
			}
			if *fv < rd.Min[i] {
				rd.Min[i] = *fv
			}
		}
	}
	rd.Cnt++
}

// Rolling calculates a rolling aggregation using the provided RollFunc and
// a duration. It handles collecting all the data in a RollData for a
// duration and handing it to the RollFunc for calculation.
func Rolling(data []SeriesResponse, f RollFunc, d time.Duration) []SeriesResponse {
	rData := make([]SeriesResponse, 0, len(data))
	queue := []*RollData{}
	for _, r := range data {
		//log.Printf("Processing %d entry %#v\n", r.Val.Value, j)
		if r.E != nil {
			rData = append(rData, r)
			continue
		}
		// push next duration slot
		// calculate all the duration slots
		//log.Println("Adding entry to queue")
		queue = append(queue, makeRollData(&r))
		nqueue := make([]*RollData, len(queue))
		copy(nqueue, queue)
		for i, rd := range nqueue {
			nts := rd.S.Add(d)
			if r.Val.Ts.Equal(nts) || r.Val.Ts.After(nts) {
				//log.Printf("Finishing %#v at index %d\n", rd, i)
				f(rd)
				rData = appendRollData(rData, rd)
				queue = queue[:remove(queue, i)]
			} else {
				//log.Printf("Updating %#v at index %d", rd, i)
				updateRollData(&r, rd)
			}
		}
	}
	if len(queue) > 1 {
		for _, rd := range queue {
			f(rd)
			rData = appendRollData(rData, rd)
		}
	}
	return rData
}

func appendRollData(data []SeriesResponse, rd *RollData) []SeriesResponse {
	vs := make([]*float64, len(rd.AccV))
	for i, fv := range rd.AccV {
		fv1 := fv
		vs[i] = &fv1
	}
	return append(data, SeriesResponse{
		Val: Datapoint{
			Ts:    rd.L,
			Value: vs,
		},
	})
}
